﻿#region Copyright information
// 
// Copyright © 2005-2013 Yongkee Cho. All rights reserved.
// 
// This code is a part of the Biological Object Library and governed under the terms of the
// GNU Lesser General  Public License (LGPL) version 2.1 which accompanies this distribution.
// For more information on the LGPL, please visit http://bol.codeplex.com/license.
// 
// - Filename: DiscreteUniformDistribution.cs
// - Author: Yongkee Cho
// - Email: yongkeecho@gmail.com
// - Date Created: 2012-09-06 11:39 AM
// - Last Modified: 2013-01-25 3:59 PM
// 
#endregion
using System;
using System.Collections.Generic;
using System.Linq;

namespace BOL.Maths.Distributions
{
    /// <summary>
    /// Represents a discrete uniform distribution over the integers from min to max.
    /// <see>Mathematica: ref/ref/DiscreteUniformDistribution</see>
    /// <see>http://en.wikipedia.org/wiki/Uniform_distribution_(discrete)</see>
    /// </summary>
    public sealed class DiscreteUniformDistribution : IUnivariateDistribution<int>, IEquatable<DiscreteUniformDistribution>
    {
        #region Private variables

        private int _min;
        private int _max;
        private readonly int _diff;

        #endregion

        #region Public properties

        /// <summary>Gets or sets the minimum of a discrete uniform distribution.</summary>
        public int Min
        {
            get { return _min; }
            set
            {
                if (value >= _max)
                    throw new ArgumentOutOfRangeException("value", "min should be less than max.");
                
                _min = value;
            }
        }

        /// <summary>Gets or sets the maximum of a discrete uniform distribution.</summary>
        public int Max
        {
            get { return _max; }
            set
            {
                if (_min >= value)
                    throw new ArgumentOutOfRangeException("value", "max should be greater than min.");
                
                _max = value;
            }
        }

        /// <summary>Gets the range of a discrete uniform distribution.</summary>
        public IRange<int> Domain { get { return new Range<int>(_min, _max); } }

        /// <summary>Gets the mean of a discrete uniform distribution.</summary>
        public double Mean { get { return (_min + _max) / 2.0; } }

        /// <summary>Gets the median of a  discrete uniform distribution.</summary>
        public double Median { get { return (_min + _max) / 2.0 ; } }

        /// <summary>Gets the mode of a  discrete uniform distribution.</summary>
        public double Mode { get { throw new Exception("Mode is undefined for uniform distribution."); } }

        /// <summary>Gets the variance of a discrete uniform distribution.</summary>
        public double Variance { get { return (Math.Pow(1 + _max - _min, 2) - 1) / 12.0; } }

        /// <summary>Gets the skewness of a  discrete uniform distribution.</summary>
        public double Skewness { get { return 0; } }

        /// <summary>Gets the kurtosis of a  discrete uniform distribution.</summary>
        public double Kurtosis { get { return -6 * (_diff * _diff - 1) / (double)(5 * (_diff * _diff + 1)); } }

        /// <summary>Gets the entropy of a  discrete uniform distribution.</summary>
        public double Entropy { get { return Math.Log(_diff); } }

        #endregion

        #region Constructor

        /// <summary>
        /// Instantiate a discrete uniform distribution.
        /// </summary>
        /// <param name="min">minimum</param>
        /// <param name="max">maximum</param>
        public DiscreteUniformDistribution(int min, int max)
        {
            if (min >= max)
                throw new ArgumentOutOfRangeException("min", "min should be less than max.");
            
            _min = min;
            _max = max;
            _diff = max - min + 1;
        }

        #endregion

        #region ICloneable implementation

        public DiscreteUniformDistribution Clone()
        {
            return new DiscreteUniformDistribution(_min, _max);
        }

        object ICloneable.Clone()
        {
            return Clone();
        }
        
        #endregion

        #region Public methods

        /// <summary>
        /// Returns the discrete uniform density given value.
        /// </summary>
        /// <param name="value"></param>
        /// <returns>discrete uniform density</returns>
        public double Pdf(int value)
        {
            return value >= _min & value <= _max ? 1.0 / _diff : 0;
        }

        /// <summary>
        /// Returns the cummulative discrete uniform density given value.
        /// </summary>
        /// <param name="value"></param>
        /// <returns>cummulative discrete uniform density</returns>
        public double Cdf(int value)
        {
            if (_min <= value & value <= _max)
                return (1 + Math.Floor((double)value) - _min) / _diff;
            
            return value > _max ? 1.0 : 0.0;
        }

        public int Quantile(double probability)
        {
            return (int)Math.Floor(probability * (_max - _min) + _min);
        }

        /// <summary>
        /// Returns a random value from the discrete uniform distribution.
        /// </summary>
        /// <returns>random value</returns>
        public int Sample(Random r)
        {
            return _min + (int)(r.NextDouble() * (_max - _min));
        }

        /// <summary>
        /// Estimates parameters of the discrete uniform distribution using maximum likelihood.
        /// <see>http://en.wikipedia.org/wiki/Uniform_distribution_(discrete)#Estimation_of_maximum</see>
        /// </summary>
        public void MaximumLikelihoodEstimate(IEnumerable<int> source)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            var list = source.ToList();
            Min = list.Min();
            Max = list.Max();
        }

        /// <summary>
        /// Estimates parameters of the discrete uniform distribution using Bayesian methods.
        /// </summary>
        public void BayesianEstimate(IEnumerable<int> source, params dynamic[] priorParameters)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IEquatable<DiscreteUniformDistribution> implementation

        public bool Equals(DiscreteUniformDistribution other)
        {
            return _max.Equals(other._max) & _min.Equals(other._min);
        }

        #endregion

        #region Object overriden

        public override int GetHashCode()
        {
            return Min.GetHashCode() ^ Max.GetHashCode();
        }

        public override bool Equals(object other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            if (!(other is DiscreteUniformDistribution))
                throw new InvalidCastException("The 'other' argument is not a DiscreteUniformDistribution object.");
            
            return Equals(other as DiscreteUniformDistribution);
        }
        
        public override string ToString()
        {
            return String.Format("DiscreteUniform(min = {0}, max = {1})", _min, _max);
        }

        #endregion
    }
}
